/* 
 * File:   mpi_ub6.cpp
 * Author: sergey
 */

#include "DefineConfiguration.h"
#include "BooleanFunction.h"
#include "LibraryInterface.h"
#include "NLLibrary.h"
#include <iostream>
#include <sstream>
#include <mpi/mpi.h>co
#include <vector>
#include <fstream>
#include <omp.h>
#include <set>
#include <queue>

const UInt8 fComplication = 14;
const UInt32 n = 5;
typedef LibraryInfo<n> CurLibInfo;

int rank = 0, size = 1;

typedef LibraryInfo<6> LIB6;


template <typename LIBINFO, int va>
inline void LPModify(std::map<UInt64, bool> &done, std::queue<UInt64> &q, const UInt64 f) {
    UInt64 tf = f;
    BooleanFunction<LIBINFO>::template derivative1<va>(tf);
    if (!done[tf]) {
        done[tf] = true;
        q.push(tf);
    }
    tf = f;
    BooleanFunction<LIBINFO>::template derivative2<va>(tf);
    if (!done[tf]) {
        done[tf] = true;
        q.push(tf);
    }
    tf = f;
    BooleanFunction<LIBINFO>::template derivative3<va>(tf);
    if (!done[tf]) {
        done[tf] = true;
        q.push(tf);
    }
    tf = f;
    BooleanFunction<LIBINFO>::template derivative4<va>(tf);
    if (!done[tf]) {
        done[tf] = true;
        q.push(tf);
    }
    tf = f;
    BooleanFunction<LIBINFO>::template negation<va>(tf);
    if (!done[tf]) {
        done[tf] = true;
        q.push(tf);
    }
    tf = f;
    BooleanFunction<LIBINFO>::template transposition<va>(tf);
    if (!done[tf]) {
        done[tf] = true;
        q.push(tf);
    }
}

template <typename LIBINFO, int n> class insertLPModify;
template <typename LIBINFO> class insertLPModify<LIBINFO, 6> {
public: inline static void go(std::map<UInt64, bool> &done, std::queue<UInt64> &q, const UInt64 f) {
    LPModify<LIBINFO, 0>(done, q, f);
    LPModify<LIBINFO, 1>(done, q, f);
    LPModify<LIBINFO, 2>(done, q, f);
    LPModify<LIBINFO, 3>(done, q, f);
    LPModify<LIBINFO, 4>(done, q, f);
    LPModify<LIBINFO, 5>(done, q, f);
}};


inline void get_lp6(const UInt64 nf, std::map<UInt64, bool> &resultMap) {    
    std::queue<UInt64> q;
    q.push(nf);
    resultMap[nf] = true;
    while(!q.empty()) {
        UInt64 tf = q.front(); q.pop();
        insertLPModify<LIB6, LIB6::n>::go(resultMap, q, tf);
    }

}

int main(int argc, char** argv) {
    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD,&rank);
    MPI_Comm_size(MPI_COMM_WORLD,&size);

    std::ostringstream filename;

    MPI_Status state;

    if (rank == 0) {
        NLLibrary<CurLibInfo> nllib;

        filename << LIBRARYDIRECTORIES << "/nllibs/nllibrary_" << CurLibInfo::n << ".bin";
        nllib.load(filename.str());
        filename.str("");

        std::ifstream fsfile;
        std::vector<std::vector<UInt64> > fs;
        fs.resize(5);
        for(size_t i = 0; i < 5; i++) {
            UInt64 size;
            filename << LIBRARYDIRECTORIES << "/fulllibs/fulllibrary_" << (int)n << "_" << i << ".bin";
            fsfile.open(filename.str().c_str(), std::ios::in | std::ios::binary);
            filename.str("");
            fsfile.seekg(0, std::ios::end);
            size = fsfile.tellg();
            fsfile.seekg(0, std::ios::beg);
            size /= 8;
            fs[i].resize(size);
            for(size_t j = 0;  j < size; j++)
                fsfile.read((char*)&fs[i][j], sizeof(UInt64));

            fsfile.close();
        }

        std::fstream resultFile;
        filename << LIBRARYDIRECTORIES << "/6_14/result.bin";              
        resultFile.open(filename.str().c_str(), std::ios::in | std::ios::binary);
        filename.str("");
        resultFile.seekg(0, std::ios::end);
        UInt64 resultFileSize = fsfile.tellg() / 8;
        resultFile.seekg(0, std::ios::beg);
        

        std::cout << "Starting task generator." << std::endl;

        std::vector<UInt64> funcs;
        funcs.resize(size - 1);
        UInt64 busy = 1;

        std::set<UInt64> resultSet;
        UInt64 counter = 0;

        while(!resultFile.eof()) {
            counter++;
            std::cout << "Processed " << std::dec << counter << " functions." << std::endl;
            UInt64 nf;
            resultFile.read((char*)&nf, sizeof(UInt64));
         
            UInt64 lpf = nf >> 32;
            UInt64 f = nf & 0xFFFFFFFF;
            UInt64 lpfpf = lpf ^ f;

            UInt64 fsi;
            UInt8 ts1, ts2, ts3, ts = 255;

            for(size_t i = 0; i < 5 && ts > fComplication; i++) {
                for(size_t j = 0; j < fs[i].size() && ts > fComplication; j++) {
                    fsi = fs[i][j];
                    nllib.get(fsi ^ lpf, ts1);
                    nllib.get(fsi ^ f, ts2);
                    if (ts1 + ts2 + i < ts) {
                        ts = ts1 + ts2 + i;
                    }
                    nllib.get(fsi ^ lpfpf, ts3);
                    if (ts1 + ts3 + i < ts) {
                        ts = ts1 + ts3 + i;
                    }
                    if (ts2 + ts3 + i < ts) {
                        ts = ts2 + ts3 + i;
                    }
                }
            }
            if (ts == fComplication) {
                if (busy == size) {
                    UInt64 recf;
                    MPI_Recv(&recf, 1, MPI_UNSIGNED_LONG_LONG, MPI_ANY_SOURCE, 3, MPI_COMM_WORLD, &state);                                        
                    resultSet.insert(recf);
                    funcs[state.MPI_SOURCE] = nf;
                    MPI_Send(&funcs[state.MPI_SOURCE], 1, MPI_UNSIGNED_LONG_LONG, state.MPI_SOURCE, 1, MPI_COMM_WORLD);
                } else {
                    for(size_t j = 0; j < funcs.size(); j++)
                        if (funcs[j] == 0) {
                            funcs[j] = nf;
                            MPI_Send(&funcs[j], 1, MPI_UNSIGNED_LONG_LONG, j + 1, 1, MPI_COMM_WORLD);
                            busy++;
                            break;
                        }
                }
            }
        }        
        resultFile.close();

        for(; busy != 1; busy--) {
            UInt64 recf;
            MPI_Recv(&recf, 1, MPI_UNSIGNED_LONG_LONG, state.MPI_SOURCE, 3, MPI_COMM_WORLD, &state);
            resultSet.insert(recf);
            MPI_Send(&busy, 1, MPI_UNSIGNED_LONG_LONG, state.MPI_SOURCE, 2, MPI_COMM_WORLD);
        }

        filename << LIBRARYDIRECTORIES << "/6_14/lpclass.bin";
        resultFile.open(filename.str().c_str(), std::ios::in | std::ios::binary);
        filename.str("");
        
        for(std::set<UInt64>::iterator i = resultSet.begin(); i != resultSet.end(); i++) {
            resultFile.write((char*)&(*i), sizeof(UInt64));
        }
        resultFile.close();

        std::cout << "Task generator ending work." << std::endl;
    } else {
        std::cout << "Starting processor: " << rank << std::endl;

        std::map<UInt64, bool> resultMap;
        std::ofstream resultFile;
        while(true) {
            UInt64 nf;
            MPI_Recv(&nf, 1, MPI_UNSIGNED_LONG_LONG, 0, MPI_ANY_TAG, MPI_COMM_WORLD, &state);            
            if (state.MPI_TAG == 1) {                
                get_lp6(nf, resultMap);
                UInt64 resf = resultMap.begin()->first;

                MPI_Send(&resf, 1, MPI_UNSIGNED_LONG_LONG, 0, 3, MPI_COMM_WORLD);

                filename << LIBRARYDIRECTORIES << "/lp0x" << std::hex << resf << ".bin";
                resultFile.open(filename.str().c_str(), std::ios::out | std::ios::binary);
                filename.str("");

                for(std::map<UInt64,bool>::iterator i = resultMap.begin(); i != resultMap.end(); i++)
                    resultFile.write((char*)&(i->first), sizeof(UInt64));
                    
                std::cout << "Function " << std::hex <<  nf << " is from class " << resf << " (" << std::dec << resultMap.size() << ")." << std::endl;                    
                
                resultMap.clear();
                resultFile.close();
            } else {
                std::cout << "Process " << std::dec << rank << " ending work." << std::endl;
                break;
            }            
        }        
    }

    MPI_Finalize();
    
    return (0);
}
